package com.millstein.util;

import com.millstein.enums.BizCodeEnum;
import com.millstein.exception.BizException;
import com.millstein.model.LoginUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;

/**
 * token扩展：
 *  1.refreshToken。在没有授权服务器的前提下，可以使用下面的逻辑
 *      a.登录成功后，jwt生成AccessToken； UUID生成RefreshToken并存储在服务端redis中,设置过期时间
 *      b.接口返回3个字段AccessToken/RefreshToken/访问令牌过期时间戳
 *      c.由于RefreshToken存储在服务端redis中，假如这个RefreshToken也过期，则提示重新登录
 *      这个方式由于没有授权服务器，也就没有客户端的名称和秘钥，所以如果被拦截，那么是可以通过refreshToken进行肆意的刷新。
 *      所以返回的token可以取别名进行混淆
 *  2.token泄漏
 *      一般使用https不会有这个问题。但万一泄漏了，可以在token的载荷中添加ip地址信息。每当请求时后端服务器都会去校验一下
 *      访问的ip地址和token中的ip地址是否一致，如果不一致不能访问。这是因为劫持者和被劫持者的ip地址肯定是不同的
 * @description:
 * @author: tsing
 * @create: 2021-09-13 15:23
 */
@Slf4j
public class JwtUtils {

    // TOKEN过期时间7天
    private static final long EXPIRE_TIME = 1000 * 60 * 60 * 24 * 7;

    // 加密的秘钥
    private static final String SECRET = "aSf24G2nvjg2ldsGPir034rlaf3ne4ksfnw49320jFs";

    // 令牌前缀
    private static final String TOKEN_PREFIX = "xdclass1024shop";

    // subject
    private static final String SUBJECT = "xdclass";

    /**
     * 生成token
     * @param loginUser 生成token时需要的信息
     * @return
     */
    public static String generateToken(LoginUser loginUser) {
        if (loginUser == null) {
            throw new BizException(BizCodeEnum.ACCOUNT_UNLOGIN);
        }
        return TOKEN_PREFIX + Jwts.builder()
                .setSubject(SUBJECT)
                .claim("head_img", loginUser.getHeadImg())
                .claim("id", loginUser.getId() + "")
                .claim("name", loginUser.getName())
                .claim("mail", loginUser.getMail())
                .setIssuedAt(new Date()) // 发布时间
                .setExpiration(new Date(CommonUtils.getCurrentTimestamp() + EXPIRE_TIME)) // 过期时间
                .signWith(SignatureAlgorithm.HS256, SECRET)
                .compact();
    }

    /**
     * 解析令牌
     * @param token 令牌
     * @return
     */
    public static Claims parseToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                    .getBody();
        } catch (Exception e) {
            log.info("jwt token解析失败");
            return null;
        }
    }
}
